<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>串口监视器</title>
    <style>
        /* 全局样式 */
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f9;
            /* display: flex; */
            /* justify-content: center; */
            align-items: center;
            /* min-height: 100vh; */
            margin: 0;
        }

        /* 容器样式 */
        .container {
            background-color: #fff;
            /* 去掉阴影 */
            /* box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); */
            padding: 20px;
            /* 让容器填满窗口 */
            width: 100%;
            /* height: 100vh; */
            box-sizing: border-box;
        }

        /* 标题样式 */
        h1 {
            font-size: 24px;
            color: #333;
            text-align: center;
            margin-bottom: 20px;
        }

        /* 表单元素容器样式 */
        .form-group {
            margin-bottom: 15px;
        }

        /* 标签样式 */
        label {
            display: inline;
            font-size: 14px;
            color: #666;
            margin-bottom: 5px;
        }

        /* 下拉选择框和输入框样式 */
        select,
        input[type="text"] {
            width: 100%;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
            transition: border-color 0.3s ease;
        }

        select:focus,
        input[type="text"]:focus {
            border-color: #007BFF;
            outline: none;
        }

        /* 按钮容器样式 */
        .button-group {
            display: flex;
            justify-content: space-between;
            margin-bottom: 15px;
        }

        /* 按钮样式 */
        button {
            padding: 10px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.3s ease;
        }

        /* 连接按钮样式 */
        #connect-button {
            background-color: #28a745;
            color: white;
        }

        #connect-button:hover {
            background-color: #218838;
        }

        /* 断开按钮样式 */
        #disconnect-button {
            background-color: #dc3545;
            color: white;
        }

        #disconnect-button:hover {
            background-color: #c82333;
        }

        #clear-button {
            background-color: white;
            border: black 1px solid;
            color: black;
            height: 25px;
            width: 40px;
            font-size: 14px;
            padding: 1px;
            position: absolute;
            right: 21px;
            top: 190px;
        }

        /* 发送按钮样式 */
        #send-button {
            background-color: #007BFF;
            color: white;
        }

        #send-button:hover {
            background-color: #0056b3;
        }

        /* 连接状态显示样式 */
        #connection-status {
            font-size: 14px;
            color: #666;
            /* margin-bottom: 15px; */
        }

        /* 自动滚动复选框容器样式 */
        .checkbox-group {
            display: block;
            align-items: center;
            margin-right: 25px;
        }

        /* 自动滚动复选框样式 */
        input[type="checkbox"] {
            margin-right: 5px;
        }

        /* 输出区域样式 */
        #output {
            font-size: 13px;
            height: calc(100vh - 200px);
            border: 1px solid #ccc;
            border-radius: 4px;
            padding: 10px;
            overflow-y: auto;
            background-color: #f9f9f9;
            margin-bottom: 15px;
        }

        /* 输入和发送区域样式 */
        .input-group {
            display: flex;
        }

        /* 输入框样式 */
        #input {
            flex: 1;
            border-top-right-radius: 0;
            border-bottom-right-radius: 0;
        }

        /* 发送按钮样式 */
        #send-button {
            border-top-left-radius: 0;
            border-bottom-left-radius: 0;
        }
    </style>
</head>

<body>
    <div class="container">
        <!-- <h1>串口监视器</h1> -->
        <!-- 波特率选择 -->
        <div class="form-group">
            <label for="baudrate-select">波特率:</label>
            <select id="baudrate-select">
                <option value="115200" selected>115200</option>
                <option value="57600">57600</option>
                <option value="38400">38400</option>
                <option value="19200">19200</option>
                <option value="14400">14400</option>
                <option value="9600">9600</option>
                <option value="4800">4800</option>
                <option value="2400">2400</option>
                <option value="1200">1200</option>
                <option value="600">600</option>
                <option value="300">300</option>
                <option value="110">110</option>
            </select>
        </div>
        <!-- 连接和断开按钮 -->
        <div class="button-group">
            <button id="connect-button">连接串口</button>
            <button id="disconnect-button" disabled>断开连接</button>
        </div>
        <div>
            <!-- 连接状态显示 -->
            <span id="connection-status">连接状态: 未连接</span>
            <!-- 自动滚动复选框 -->
            <span class="checkbox-group">
                <input type="checkbox" id="auto-scroll-checkbox" checked>
                <label for="auto-scroll-checkbox">自动滚动</label>
            </span>
        </div>
        <button id="clear-button" onclick="output.innerHTML=''">清空</button>
        <!-- 输出区域 -->
        <div id="output"></div>
        <!-- 输入和发送区域 -->
        <div class="input-group">
            <input type="text" id="input" placeholder="输入要发送的消息" disabled>
            <button id="send-button" disabled>发送</button>
        </div>
    </div>

    <script>
        let port;
        let writer;
        let reader;
        let autoScroll = true;
        const MAX_OUTPUT_LINES = 300000; // 最大行数

        const connectButton = document.getElementById('connect-button');
        const disconnectButton = document.getElementById('disconnect-button');
        const output = document.getElementById('output');
        const input = document.getElementById('input');
        const sendButton = document.getElementById('send-button');
        const baudrateSelect = document.getElementById('baudrate-select');
        const connectionStatus = document.getElementById('connection-status');
        const autoScrollCheckbox = document.getElementById('auto-scroll-checkbox');

        connectButton.addEventListener('click', async () => {
            try {
                // 请求用户选择一个串口设备
                port = await navigator.serial.requestPort();
                const baudRate = parseInt(baudrateSelect.value, 10);
                // 打开串口连接，使用用户选择的波特率
                await port.open({ baudRate });

                writer = port.writable.getWriter();
                reader = port.readable
                   .pipeThrough(new TextDecoderStream())
                   .getReader();

                connectButton.disabled = true;
                disconnectButton.disabled = false;
                input.disabled = false;
                sendButton.disabled = false;

                // 获取串口信息
                const portInfo = port.getInfo();
                let portName = portInfo.path;
                if (!portName) {
                    if (portInfo.usbVendorId && portInfo.usbProductId) {
                        portName = `USB 设备 (厂商 ID: ${portInfo.usbVendorId}, 产品 ID: ${portInfo.usbProductId})`;
                    } else {
                        portName = '未知串口';
                    }
                }

                // 更新连接状态和已连接串口信息
                connectionStatus.textContent = `连接状态: 已连接到 ${portName}`;

                readFromSerial();
            } catch (error) {
                console.error('连接串口时出错:', error);
            }
        });

        disconnectButton.addEventListener('click', async () => {
            try {
                if (reader) {
                    try {
                        await reader.cancel();
                    } catch (e) { }
                    try {
                        reader.releaseLock();
                    } catch (e) { }
                }
                if (writer) {
                    try {
                        await writer.close();
                    } catch (e) { }
                    try {
                        writer.releaseLock();
                    } catch (e) { }
                }
                if (port) {
                    try {
                        await port.close();
                    } catch (e) { }
                }

                connectButton.disabled = false;
                disconnectButton.disabled = true;
                input.disabled = true;
                sendButton.disabled = true;

                // 更新连接状态
                connectionStatus.textContent = '连接状态: 未连接';
            } catch (error) {
                console.error('断开串口连接时出错:', error);
            }
        });

        sendButton.addEventListener('click', async () => {
            const message = input.value;
            if (message) {
                try {
                    const encoder = new TextEncoder();
                    const data = encoder.encode(message);
                    await writer.write(data);
                    input.value = '';
                } catch (error) {
                    console.error('发送数据时出错:', error);
                }
            }
        });

        autoScrollCheckbox.addEventListener('change', () => {
            autoScroll = autoScrollCheckbox.checked;
        });

        async function readFromSerial() {
            while (port.readable) {
                try {
                    const { value, done } = await reader.read();
                    if (done) {
                        reader.releaseLock();
                        break;
                    }

                    const fragment = document.createDocumentFragment();
                    // 处理换行符，将换行符替换为 HTML 的换行标签
                    const processedValue = value.replace(/\n/g, '<br>');
                    const tempDiv = document.createElement('div');
                    tempDiv.innerHTML = processedValue;
                    const nodes = tempDiv.childNodes;
                    for (let i = 0; i < nodes.length; i++) {
                        fragment.appendChild(nodes[i].cloneNode(true));
                    }

                    output.appendChild(fragment);

                    // 限制输出行数
                    while (output.children.length > MAX_OUTPUT_LINES) {
                        output.removeChild(output.firstChild);
                    }

                    // 根据自动滚动状态决定是否滚动到最新内容
                    if (autoScroll) {
                        output.scrollTop = output.scrollHeight;
                    }
                } catch (error) {
                    console.error('读取串口数据时出错:', error);
                    break;
                }
            }
        }
    </script>
</body>

</html>